package com.hy.formula.scanner;

import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import com.hy.formula.common.CollectionUtils;
import com.hy.formula.common.Symbol;
import com.hy.formula.entity.Token;

/**
 * 词法分析器
 * @author huangy
 * @date 2015年4月15日
 */
public class Scanner {
	
	/**
	 * 扫描公式，解析出符号项
	 * @param formula
	 * @return
	 */
	public List<Token> getTokens(String formula){
		List<Token> tokenList = new LinkedList<Token>();
		
		//字符串缓冲
		StringBuilder val = new StringBuilder();
		//当前字符串可能的符号集合
		Set<Symbol> symbolSet = new LinkedHashSet<Symbol>();
		Character lastC = null;
		for(int i=0;i<formula.length();i++){
			char c = formula.charAt(i);
			if(c == ' '){
				continue;
			}else if((c >= 'a' && c <='z') || (c >='A' && c <='Z')){
				// 字符串或是函数名
				val.append(c);
				//添加可能性
				
				if(!symbolSet.contains(Symbol.STRING)){
					symbolSet.add(Symbol.STRING);
				}else if(symbolSet.contains(Symbol.NUMBER)){
					//不可能是数字里包含着字母
					throw new NumberFormatException(val.toString());
				}
			}else if(c == '-'){
				if(CollectionUtils.isEmpty(symbolSet)) {
					//负数
					val.append(c);
					symbolSet.add(Symbol.NUMBER);
				}else {
					//减号，终结符，之前可能是数字也可能是变量名
					//如果这时候symbol是包含数字的变量名，那linkHashSet第一个只可能是字符串
					//所以这里取第一个符号没毛病
					buildNumAndOpToken(tokenList, val, symbolSet, c);
				}
			}else if( c =='+') {
				buildNumAndOpToken(tokenList, val, symbolSet, c);
			}else if( c =='*') {
				buildNumAndOpToken(tokenList, val, symbolSet, c);
			}else if( c =='/') {
				buildNumAndOpToken(tokenList, val, symbolSet, c);
			}else if(c>='0' && c<='9'){
				val.append(c);
				//添加可能性
				if(!symbolSet.contains(Symbol.STRING) 
						&& !symbolSet.contains(Symbol.NUMBER)){
					//不是函数名或是字符串，那有可能是数值
					symbolSet.add(Symbol.NUMBER);
				}
			}else if(c == '('){
				//终结符
				//括号前是函数名称，保存成一个token
				Token funcNametoken = new Token();
				funcNametoken.setSymbol(Symbol.FUNC);
				funcNametoken.setValue(val.toString());
				val.delete(0, val.length());
				tokenList.add(funcNametoken);
				//这里确定了是token是方法名，清除符号猜测
				symbolSet.clear();
				
				//把括号独立存储成一个token
				Token leftParenthisisToken = new Token();
				leftParenthisisToken.setSymbol(Symbol.LEFT_PARENTHESIS);
				leftParenthisisToken.setValue("(");
				tokenList.add(leftParenthisisToken);
			}else if(c == ')'){
				//终结符
				if(val.length() > 0){
					//可能有嵌套函数的情况
					Token paramToken = new Token();
					paramToken.setSymbol(symbolSet.toArray(new Symbol[0])[0]);
					paramToken.setValue(val.toString());
					val.delete(0, val.length());
					tokenList.add(paramToken);
					symbolSet.clear();
				}else if(val.length() == 0 && lastC == '('){
					//没有参数的情况
					//放入空值
					Token paramToken = new Token();
					paramToken.setSymbol(Symbol.VOID);
					tokenList.add(paramToken);
				}
				
				//把括号独立存储成一个token
				Token rightParenthisisToken = new Token();
				rightParenthisisToken.setSymbol(Symbol.RIGHT_PARENTHESIS);
				rightParenthisisToken.setValue(")");
				tokenList.add(rightParenthisisToken);
			}else if(c == ','){
				//终结符
				if(val.length() > 0){
					//可能有嵌套函数的情况
					Token paramToken = new Token();
					paramToken.setSymbol(symbolSet.toArray(new Symbol[0])[0]);
					paramToken.setValue(val.toString());
					val.delete(0, val.length());
					tokenList.add(paramToken);
					symbolSet.clear();
				}
				
				//把逗号独立存储成一个token
				Token commaToken = new Token();
				commaToken.setSymbol(Symbol.COMMA);
				commaToken.setValue(",");
				tokenList.add(commaToken);
			}else if(c == '.'){
				//小数符号
				if(val.indexOf(".") >= 0){
					throw new NumberFormatException("had contain dot(.)");
				}
				val.append(c);
			}else{
				val.append(c);
			}
			lastC = c;
		}
		//包含运算符的表达式没有终结符，需要额外把缓存里的保存到tokenList中
		if(val.length() > 0) {
			Token extToken = new Token();
			extToken.setSymbol(symbolSet.toArray(new Symbol[0])[0]);
			extToken.setValue(val.toString());
			tokenList.add(extToken);
		}
		return tokenList;
	}

	private void buildNumAndOpToken(List<Token> tokenList, StringBuilder val, Set<Symbol> symbolSet, char c) {
		Token numberToken = new Token();
		numberToken.setSymbol(symbolSet.toArray(new Symbol[0])[0]);
		numberToken.setValue(val.toString());
		tokenList.add(numberToken);
		symbolSet.clear();
		val.delete(0, val.length());
		
		//运算符单独保存一个token
		Token opToken = new Token();
		opToken.setSymbol(Symbol.OP);
		opToken.setValue(String.valueOf(c));
		tokenList.add(opToken);
	}
	
	
	
}
